import Log from './log'
import Consts from '../assets/consts'
import NIM from './vender/NIM_Web_NIM_v6.2.0'

import db from './db'
import DelayPromise from './delay.promise'

let log = new Log('nim>')
let dp = null

let sdk = null;
let currentAccount = ''

let userDate = {}

let s2a1 = data => 'success' === data.status ? arg1.success : arg1.fail

let sidTodo = sid => {
  let to = sid.split('-')

  if (null != to && 2 === to.length
      && (('team' === to[0] && to[1].length > 0)
          || ('p2p' === to[0] && to[1].startsWith(Consts.imAppSecret)))) {
  } else {
    throw new Error('bad sid')
  }

  return to
}

let teamCbTodo = data => {
  return {
    team: data.teamId || (data.team ? data.team.teamId : null),
    name: data.name || (data.team ? data.team.name : null),

    owner: data.team ? data.team.owner : null,
    accounts: data.accounts || null,

    origin: data
  }
}

let msgTodo = msg => {
  return {
    session: msg.sessionId,
    ts: msg.time,
    status: msg.status,

    type: msg.type,
    text: msg.text || null,
    custom: msg.custom || null,
    content: msg.content || null,

    // [Neo] .file
    file: msg.file || null,
    // [Neo] .notification
    attach: msg.attach || null,

    scene: msg.scene,
    from: msg.from,
    to: msg.to,
    id: msg.idClient,

    origin: msg
  }
}

let sessionTodo = s => {
  const to = sidTodo(s.id)

  return {
    session: s.id,
    unread: s.unread,

    ts: s.updateTime,
    remote: s.msgReceiptTime || 0,

    pin: 0,
    mute: 0,
    ignore: 0,

    scene: to[0],
    target: to[1],

    message: msgTodo(s.lastMsg),
    origin: s,
  }
}

let id4session = sid => {
  const to = sidTodo(sid)

  return {
    session: sid,
    unread: 0,

    ts: 0,
    remote: 0,

    pin: 0,
    mute: 0,
    ignore: 0,

    scene: to[0],
    target: to[1],

    message: null,
    origin: null
  }
}

let msg4session = (msg, ignore = 0) => {
  const to = sidTodo(msg.session)

  return {
    session: msg.session,
    unread: 0,

    ts: msg.ts,
    remote: 0,

    pin: 0,
    mute: 0,
    ignore: ignore,

    scene: to[0],
    target: to[1],

    message: msg,
    origin: null
  }
}

let msgs2session = msgs => {
  const count = msgs.length
  let lastMsg = msgs[count - 1]

  db.open(dbName + currentAccount)
  .then(e => db.update(e, tables.session.name, lastMsg.session, v => {
    if (null == v) {
      v = msg4session(lastMsg)
    }

    msgs.forEach(m => {
      if (m.ts > v.ignore) {
        v.unread += 1
      }
    })

    v.ts = v.ts > lastMsg.ts ? v.ts : lastMsg.ts
    v.message = lastMsg

    return v
  }))
  .then(e => db.add(e, tables.message.name, msgs))
  .catch(e => log.e(e))
}

let sortByPinAndTs = (a, b) => {
  if (1 === a.pin && 0 === b.pin) {
    return -1;
  } else if (0 === a.pin && 1 === b.pin) {
    return 1;
  } else {
    return b.ts - a.ts;
  }
}

let _handler = null;
let _msg_cb = null;
let connected = false;
let synced = false;

const what = Consts.im.what;
const arg1 = Consts.im.arg1;
const arg2 = Consts.im.arg2;

let sendDone = (key, msg, err) => {
  db.open(dbName + currentAccount)
  .then(e => db.add(e, tables.message.name, msg))
  .then(e => db.update(e, tables.session.name, msg.session, (v) => {
    if (null == v) {
      v = msg4session(msg)
    }

    v.unread = 0
    v.ts = v.ts > msg.ts ? v.ts : msg.ts
    v.message = msg

    return v
  }))
  .then(_ => dp.done(key, err, msg))
  .catch(e => dp.done(key, e, null))
}

let cb = {
  onLog: data => {
    log.d('onLog', data)
  },

  onSendDone: (err, obj) => {
    const msg = msgTodo(obj)
    const key = JSON.parse(msg.custom)._dp_key
    sendDone(key, msg, err)
  },

  onConnect: data => {
    // [Neo] .TODO add init data
    userDate = {}

    connected = true;
    synced = false;

    dp.done('im-connect', null, data);

    if (null != _handler) {
      _handler(what.connected, arg1.success, arg2.more, data)
    }
  },

  onWillReconnect: data => {
    if (null != _handler) {
      _handler(what.disconnectWillConnect, arg1.success, arg2.more, data)
    }
  },

  onDisconnect: data => {
    // callFunc, code, event, message, timetag, willReconnect
    connected = false;
    synced = false;

    dp.done('im-disconnect', null, data);

    if (null != _handler) {
      _handler(what.disconnect, arg1.success, arg2.more, data)
    }
  },

  onError: e => log.e('onError', e),
  onLoginPortChange: data => log.e('onLoginPortChange', data),

  // onSyncMarkinBlacklist: data => log.d('onSyncMarkinBlacklist', data),
  // onMuteList: list =>log.d('onMuteList', list),
  // onSyncMarkinMuteList: data => log.d('onSyncMarkinMuteList', data),

  onTeams: list => {
    // teamId, owner, createTime, memberNum, mute, muteType, name
    // valid, validToCurrentUser
    // chatGroups[currentAccount] = {}

    list.forEach(l => {
      if (l.valid && l.validToCurrentUser) {
        // [Neo] .TODO
        // chatGroups[currentAccount][l.teamId] =
        const t = {
          id: 'team-' + l.teamId,
          name: l.name,
          owner: l.owner,
          count: l.memberNum,
          ts: l.updateTime,
          tid: l.teamId,

          origin: l,
        }
      }
    })
  },

  onSyncCreateTeam: data => log.w('onSyncCreateTeam', data),
  onTeamMembers: data => log.w('onTeamMembers', data),
  onSyncTeamMembersDone: data => log.w('onSyncTeamMembersDone', data),
  onUpdateTeamMember: data => log.w('onUpdateTeamMember', data),

  // [Neo] .TODO
  onCreateTeam: data => log.w('onCreateTeam', data),

  onUpdateTeam: data => {
    if (null != _handler) {
      _handler(what.notify, arg1.success, arg2.teamUpdate,
          teamCbTodo(data))
    }
  },

  onAddTeamMembers: data => {
    const t = teamCbTodo(data)
    const sid = 'team-' + t.team

    db.open(dbName + currentAccount)
    .then(e => db.update(e, tables.session.name, sid,
        v => (null == v) ? id4session(sid) : null))
    .then(_ => {
      if (null != _handler) {
        _handler(what.notify, arg1.success, arg2.teamChangeForSessionUpdate, t)
      }
    })
    .catch(_ => {
      if (null != _handler) {
        _handler(what.notify, arg1.success, arg2.teamAddMembers, t)
      }
    })

  },

  onRemoveTeamMembers: data => {
    const t = teamCbTodo(data)
    const sid = 'team-' + t.team

    db.open(dbName + currentAccount)
    .then(e => db.get(e, tables.session.name, sid))
    .then(e => {
      let deleteMe = false
      e.data.accounts.forEach(a => {
        if (currentAccount === a) {
          deleteMe = true
        }
      })

      if (deleteMe) {
        return db.delete(e, tables.session.name, sid)
      } else {
        return dp.bad()
      }
    })
    .then(_ => {
      if (null != _handler) {
        _handler(what.notify, arg1.success, arg2.teamChangeForSessionUpdate, t)
      }
    })
    .catch(_ => {
      if (null != _handler) {
        _handler(what.notify, arg1.success, arg2.teamRemoveMembers, t)
      }
    })

  },

  onUpdateTeamMembers: data => {
    if (null != _handler) {
      _handler(what.notify, arg1.success, arg2.teamUpdateMembers,
          teamCbTodo(data))
    }
  },

  onDismissTeam: data => {
    if (null != _handler) {
      _handler(what.notify, arg1.success, arg2.teamDismiss,
          teamCbTodo(data))
    }
  },

  onTransferTeam: data => {
    if (null != _handler) {
      _handler(what.notify, arg1.success, arg2.teamTransfer, teamCbTodo(data))
    }
  },

  onUpdateTeamMembersMute: data => log.w('onUpdateTeamMembersMute', data),

  onSessions: list => {
    db.open(dbName + currentAccount)
    .then(e => {
      let ss = list.map(sessionTodo)
      db.updateBatch(e, tables.session.name,
          ss.map(l => l.session), ss, (old, s) => {
            let session = null != old ? old : s

            if (s.ts > session.ignore) {
              s.ignore = session.ignore
              s.pin = session.pin
              s.mute = session.mute

              // [Neo] .TODO ignore other type

              session = s
            }

            return session
          })
    })
    .catch(e => log.e(e))
  },

  onUpdateSessions: data => log.d('onUpdateSessions', data),

  // attach: type ...
  // type, flow, scene, from, to, target, text, sessionId (scene-target)
  shouldIgnoreNotification: data => log.v('shouldIgnoreNotification', data),
  shouldCountTeamNotifyUnread: data =>
      log.d('shouldCountTeamNotifyUnread', data),

  onRoamingMsgs: data => {
    msgs2session(data.msgs.map(msgTodo))
  },

  onOfflineMsgs: list => {
    if (Array.isArray(list)) {
      list.forEach(l => msgs2session(l.msgs.map(msgTodo)))
    } else {
      msgs2session(msgTodo(list))
    }
  },

  onMsg: msg => {
    // status
    // scene, from, to, target, sessionId (scene-target)
    // type, text,
    // time, userUpdateTime
    // attach: accounts, members, team, type, users

    if ('notification' === msg.type) {
      switch (msg.attach.type) {
        case 'addTeamManagers':
          if (null != _handler) {
            _handler(what.notify, arg1.success, arg2.teamAddManagers,
                teamCbTodo(msg.attach))
          }
          break

        case 'removeTeamManagers':
          if (null != _handler) {
            _handler(what.notify, arg1.success, arg2.teamRemoveManagers,
                teamCbTodo(msg.attach))
          }
          break

        default:
          break
      }
    }

    let m = msgTodo(msg)

    db.open(dbName + currentAccount)
    .then(e => db.update(e, tables.session.name, m.session, (v) => {
      if (null == v) {
        v = msg4session(m)
        log.w('onMsg for new session', v)
      }

      v.unread += 1
      v.ts = v.ts > m.ts ? v.ts : m.ts
      v.message = m

      // [Neo] .mute filter
      if (!v.mute) {
        if (null != _msg_cb) {
          _msg_cb(m)
        }
      }

      return v
    }))
    .then(_ => {
      if (null != _handler) {
        _handler(what.receive, s2a1(msg), arg2.normal, m)
      }
    })
    .catch(e => log.e(e))
  },

  onOfflineSysMsgs: data => log.d('onOfflineSysMsgs', data),
  onRoamingSysMsgs: data => log.v('onRoamingSysMsgs', data),
  onSysMsg: data => log.d('onSysMsg', data),
  onUpdateSysMsg: data => log.d('onUpdateSysMsg', data),

  // deleteMsg?, msg, total
  onSysMsgUnread: list => log.v('onSysMsgUnread', list),
  onUpdateSysMsgUnread: data => log.d('onUpdateSysMsgUnread', data),

  // [Neo] .TODO
  onOfflineCustomSysMsgs: list => {
    log.d('onOfflineCustomSysMsgs', list);
    if (null != _handler) {
      _handler(what.receive, arg1.success, arg2.system, list)
    }
  },

  // [Neo] .TODO
  onCustomSysMsg: data => {
    log.d('onCustomSysMsg', data);
    if (null != _handler) {
      _handler(what.receive, arg1.success, arg2.system, data)
    }
  },

  onSyncDone: _ => {
    synced = true;
    dp.done('im-init', null, nim.session.list())

    if (null != _handler) {
      _handler(what.synced, arg1.success, arg2.none)
    }
  },
}

const defaultOptions = {
  db: true,
  debug: false,
  secure: true,
  autoMarkRead: true,
  syncMsgReceipts: true,
  syncRoamingMsgs: true,

  // [Neo] .change defaults
  syncSessionUnread: true,
  syncRelations: false,
  syncTeams: false,

  // [Neo] .deprecated
  // syncTeamMembers: true,

  // logFunc: cb.onLog,

  // onmutelist: cb.onMuteList,
  // onsyncmarkinblacklist: cb.onSyncMarkinBlacklist,
  // onsyncmarkinmutelist: cb.onSyncMarkinMuteList,

  onmsg: cb.onMsg,
  onconnect: cb.onConnect,
  ondisconnect: cb.onDisconnect,
  onteams: cb.onTeams,
  onsessions: cb.onSessions,
  onsysmsgunread: cb.onSysMsgUnread,
  onsyncdone: cb.onSyncDone,
  shouldIgnoreNotification: cb.shouldIgnoreNotification,
  onAddTeamMembers: cb.onAddTeamMembers,
  onwillreconnect: cb.onWillReconnect,
  onerror: cb.onError,
  onloginportchange: cb.onLoginPortChange,
  onsynccreateteam: cb.onSyncCreateTeam,
  onteammembers: cb.onTeamMembers,
  onsyncteammembersdone: cb.onSyncTeamMembersDone,
  onupdateteammember: cb.onUpdateTeamMember,
  onCreateTeam: cb.onCreateTeam,
  onUpdateTeam: cb.onUpdateTeam,
  onRemoveTeamMembers: cb.onRemoveTeamMembers,
  onUpdateTeamMembers: cb.onUpdateTeamMembers,
  onDismissTeam: cb.onDismissTeam,
  onTransferTeam: cb.onTransferTeam,
  onUpdateTeamMembersMute: cb.onUpdateTeamMembersMute,
  onupdatesessions: cb.onUpdateSessions,
  shouldCountTeamNotifyUnread: cb.shouldCountTeamNotifyUnread,
  onroamingmsgs: cb.onRoamingMsgs,
  onofflinemsgs: cb.onOfflineMsgs,
  onofflinesysmsgs: cb.onOfflineSysMsgs,
  onroamingsysmsgs: cb.onRoamingSysMsgs,
  onsysmsg: cb.onSysMsg,
  onupdatesysmsg: cb.onUpdateSysMsg,
  onupdatesysmsgunread: cb.onUpdateSysMsgUnread,
  onofflinecustomsysmsgs: cb.onOfflineCustomSysMsgs,
  oncustomsysmsg: cb.onCustomSysMsg,
}

const dbName = 'im-'
const dbVersion = 0
const tables = {
  session: {
    name: 'session',
    key: 'session',
    // refresh: true,
    indexes: [
      {
        name: 'ts',
      },
      {
        name: 'pin',
      },
      {
        name: 'mute',
      },
    ]
  },

  message: {
    name: 'message',
    key: 'id',
    // refresh: true,
    indexes: [
      {
        name: 'session',
      },
      {
        name: 'ts',
      },
      {
        name: 'read',
      },
    ]
  },
}

let nim = {
  init: (options, handler) => {
    if (synced && connected) {
      if (options.account === currentAccount) {
        log.w('reinit, just return session.list')
        return nim.session.list()
      } else {
        log.w('reinit different account, disconnect & init')
        return nim.disconnect().then(_ => nim.init(options, handler))
      }
    }

    let nimOptions = Object.assign({}, defaultOptions, options)
    dp = new DelayPromise(nimOptions.debug)

    // [Neo] .TODO
    nimOptions.debug = false

    const key = 'im-init'
    dp.prepare(key)
    currentAccount = options.account

    db.open(dbName + currentAccount, tables, dbVersion)
    .then(_ => {
      sdk = NIM.getInstance(nimOptions)
      _handler = handler
    })
    .catch(e => dp.done(key, e))

    return dp.promise(key)
  },

  setHandler: handler => _handler = handler,

  setMsgCb: cb => _msg_cb = cb,

  connect: _ => {
    const key = 'im-connect'
    dp.prepare(key);

    sdk.connect();

    return dp.promise(key)
  },

  disconnect: _ => {
    const key = 'im-disconnect'
    dp.prepare(key);

    sdk.disconnect();

    return dp.promise(key)
  },

  connected: _ => connected,
  synced: _ => synced,

  session: {
    pin: sid => {
      const isArray = Array.isArray(sid)
      const key = isArray
          ? dp.genKey('pin-' + sid[0] + '-' + sid.length)
          : dp.genKey('pin-' + sid)
      dp.prepare(key)

      db.open(dbName + currentAccount)
      .then(e => {
        if (isArray) {
          return db.updateBatch(e, tables.session.name, sid, sid, (v, s) => {
            if (null == v) {
              v = id4session(s)
            }

            v.pin = 1
            v.ts = new Date().getTime()

            return v
          })
        } else {
          return db.update(e, tables.session.name, sid, v => {
            if (null == v) {
              v = id4session(sid)
            }

            v.pin = 1
            v.ts = new Date().getTime()

            return v
          })
        }

      })
      .then(_ => dp.done(key))
      .catch(e => dp.done(key, e))

      return dp.promise(key)
    },

    unpin: sid => {
      const isArray = Array.isArray(sid)
      const key = isArray
          ? dp.genKey('unpin-' + sid[0] + '-' + sid.length)
          : dp.genKey('unpin-' + sid)
      dp.prepare(key)

      db.open(dbName + currentAccount)
      .then(e => {
        if (isArray) {
          return db.updateBatch(e, tables.session.name, sid, sid, (v, s) => {
            if (null == v) {
              v = id4session(s)
            }

            v.pin = 0
            return v
          })
        } else {
          return db.update(e, tables.session.name, sid, v => {
            if (null == v) {
              v = id4session(sid)
            }

            v.pin = 0
            return v
          })
        }
      })
      .then(_ => dp.done(key, null, null))
      .catch(e => dp.done(key, e, null))

      return dp.promise(key)
    },

    mute: sid => {
      const isArray = Array.isArray(sid)
      const key = isArray
          ? dp.genKey('mute-' + sid[0] + '-' + sid.length)
          : dp.genKey('mute-' + sid)
      dp.prepare(key)

      db.open(dbName + currentAccount)
      .then(e => {
        if (isArray) {
          return db.updateBatch(e, tables.session.name, sid, sid, (v, s) => {
            if (null == v) {
              v = id4session(s)
            }

            v.mute = 1
            return v
          })
        } else {
          return db.update(e, tables.session.name, sid, v => {
            if (null == v) {
              v = id4session(sid)
            }

            v.mute = 1
            return v
          })
        }
      })
      .then(_ => dp.done(key, null, null))
      .catch(e => dp.done(key, e, null))

      return dp.promise(key)
    },

    unmute: sid => {
      const isArray = Array.isArray(sid)
      const key = isArray
          ? dp.genKey('unmute-' + sid[0] + '-' + sid.length)
          : dp.genKey('unmute-' + sid)
      dp.prepare(key)

      db.open(dbName + currentAccount)
      .then(e => {
        if (isArray) {
          return db.updateBatch(e, tables.session.name, sid, sid, (v, s) => {
            if (null == v) {
              v = id4session(s)
            }

            v.mute = 0
            return v
          })
        } else {
          return db.update(e, tables.session.name, sid, v => {
            if (null == v) {
              v = id4session(sid)
            }

            v.mute = 0
            return v
          })
        }
      })
      .then(_ => dp.done(key, null, null))
      .catch(e => dp.done(key, e, null))

      return dp.promise(key)
    },

    read: sid => {
      const key = 'im-session-read-' + sid
      dp.prepare(key)

      sdk.resetSessionUnread(sid)

      db.open(dbName + currentAccount)
      .then(e => db.update(e, tables.session.name, sid, v => {
        if (null == v) {
          v = id4session(sid)
        }

        v.unread = 0
        return v
      }))
      .then(_ => dp.done(key))

      return dp.promise(key)
    },

    list: _ => {
      const key = dp.genKey('im-session-list-' + currentAccount)
      dp.prepare(key)

      db.open(dbName + currentAccount)
      .then(e => db.list(e, tables.session.name, null, null, (v) => {
        return v.ts > v.ignore
      }))
      .then(e => {
        let list = e.result
        if (null != list) {
          list.sort(sortByPinAndTs)
        }

        dp.done(key, null, list)
      })
      .catch(e => dp.done(key, e, null))

      return dp.promise(key)
    },

    delete: (sid, remote = false) => {
      const to = sidTodo(sid)
      const key = dp.genKey('im-chat-delete-' + sid)
      dp.prepare(key)

      sdk.deleteLocalSession({
        id: sid,
        done: (err, _) => {
          db.open(dbName + currentAccount)
          .then(e => db.update(e, tables.session.name, sid, (v) => {
            if (null == v) {
              v = id4session(sid)
            }

            v.ignore = new Date().getTime()
            return v
          }))
          .then(_ => {
            if (!remote) {
              dp.done(key, err, null)
            }
          })
          .catch(e => log.e(e))
        }
      })

      if (remote) {
        sdk.deleteSession({
          scene: to[0],
          to: to[1],
          done: (err, _) => {
            dp.done(key, err, nim.session.list())
          }
        })
      }

      return dp.promise(key)
    },

    clean: (remote = false) => {
      const key = dp.genKey('im-chat-clean')
      dp.prepare(key)

      if (remote) {
        db.open(dbName + currentAccount)
        .then(e => db.list(e, tables.session.name))
        .then(e => {
          const ss = e.result.map(s => {
            const to = sidTodo(s.session)
            return {
              scene: to[0],
              target: to[1],
            }
          })

          sdk.deleteSessions({
            sessions: ss,
            done: (err, obj) => {
              log.d('clean sessions', err, obj)
              dp.done(key, err, obj)
            }
          })
        }).catch(e => log.e(e))
      }

      db.open(dbName + currentAccount)
      .then(e => db.truncate(e, tables.session.name))
      .then(_ => {
        if (!remote) {
          dp.good()
        }
      })
      .catch(e => dp.bad(e))

      sdk.deleteAllLocalSysMsgs({
        done: (err, obj) => {
          log.v('clean sys msgs', err, obj)
        }
      })

      sdk.deleteAllLocalMsgs({
        done: (err, obj) => {
          log.v('clean msgs', err, obj)
        }
      })

      return dp.promise(key)
    },
  },

  message: {
    resend: (message, autoBurn = false, timeout = 30) => {
      // [Neo] .TODO
      const key = message.origin.custom._dp_key
      dp.prepare(key)

      sdk.resendMsg({
        msg: message.origin,
        done: cb.onSendDone
      })

      dp.promise(key)
    },

    send: (sid, message, autoBurn = false, timeout = 30) => {
      const to = sidTodo(sid)
      const key = dp.genKey('im-send')

      const merge = {
        scene: to[0],
        to: to[1],
        type: ('file' === message.type) ?
            message.fileType || 'file' : message.type,
        content: JSON.stringify(message.content),
        custom: JSON.stringify(Object.assign({_dp_key: key}, message.custom)),
        needMsgReceipt: 'p2p' === to[0],
        done: cb.onSendDone,
      }
      const msg = Object.assign({}, message, merge)

      dp.prepare(key)

      switch (message.type) {
        case 'custom':
          sdk.sendCustomMsg(msg)
          break;

        case 'file':
          sdk.sendFile(msg)
          break;

        case 'text':
        default:
          sdk.sendText(msg)
          break;
      }

      return dp.promise(key)
    },

    isRead: message => {
      let result = false

      if (message.session.startsWith('p2p')) {
        result = sdk.isMsgRemoteRead(message.origin)
      }

      return result
    },

    read: message => {
      const to = sidTodo(message.session)
      const key = dp.genKey('im-message-read-' + message.session)
      dp.prepare(key)

      db.open(dbName + currentAccount)
      .then(e => db.update(e, tables.session.name, message.session, (v) => {
        if (null == v) {
          v = id4session(message.session)
        }

        v.unread = 0
        return v
      }))
      .catch(e => log.e(e))

      if (to[0].startsWith('p2p')) {
        sdk.sendMsgReceipt({
          msg: message.origin,
          done: (err, obj) => {
            dp.done(key, err, obj)
          }
        })
      } else {
        dp.done(key, 'not support')

        // [Neo] .ignore
        // sdk.sendTeamMsgReceipt({
        //   teamMsgReceipts: [{
        //     teamId: to[1],
        //     idServer: message.origin.idServer,
        //   }],
        //   done: (err, obj) => {
        //     dp.done(key, err, obj)
        //   }
        // })
      }

      return dp.promise(key)
    },

    recall: message => {
      return nim.message.delete(message, true)
    },

    delete: (message, remote = false) => {
      const key = 'im-message-delete-' + message.id
      dp.prepare(key)

      sdk.deleteLocalMsg({
        msg: message.origin,
        done: err => {

          db.open(dbName + currentAccount)
          .then(e => db.delete(e, tables.message.name, message.id))
          .then(_ => {
            if (!remote) {
              dp.done(key, err, null)
            }
          })
          .catch(e => dp.done(key, e, null))

        }
      })

      if (remote) {
        sdk.deleteMsg({
          msg: message.origin,
          done: err => {
            dp.done(key, err, null)
          }
        })
      }

      return dp.promise(key)
    },

    forward: (sid, message) => {
      const to = sidTodo(sid)
      const key = 'im-message-forward-' + sid + '-' + message.id
      dp.prepare(key)

      sdk.forwardMsg({
        msg: message,
        scene: to[0],
        to: to[1],
        done: (err, obj) => {
          // [Neo] .TODO test
          sendDone(key, msgTodo(obj), err)
        }
      })

      return dp.promise(key)
    },

    list: (sid, ts = null, limit = 50, asc = true, remote = false) => {
      const to = sidTodo(sid)
      const end = (null === ts) ? new Date().getTime() : ts
      const key = 'im-message-list-' + sid + '-' + end
      dp.prepare(key)

      let done = (err, obj) => dp.done(key, err,
          !err ? obj.msgs.map(msgTodo) : null)

      if (remote) {
        sdk.getHistoryMsgs({
          scene: to[0],
          to: to[1],
          endTime: end,
          limit: limit,
          asc: asc,
          done: done,
        })
      } else {
        sdk.getLocalMsgs({
          sessionId: sid,
          end: end,
          limit: limit,
          desc: !asc,
          done: done,
        })
      }

      return dp.promise(key)
    },

    search: (sid, key) => {
      // [Neo] .TODO local db need

    },

  },

  user: {
    info: account => {
      return userDate.hasOwnProperty(account) ?
          dp.good(userDate[account]) : nim.user.fetch(account)
    },

    fetch: account => {
      const key = dp.genKey('im-user-' + account)
      dp.prepare(key)

      sdk.getUser({
        account: account,
        done: (err, obj) => {
          userDate[obj.account] = obj
          dp.done(key, err, obj)
        }
      })

      return dp.promise(key)
    },

  },

  conference: {
    add: (audio = true, name, users) => {

    },

    ready: sid => {

    },

    quit: sid => {

    },

    list: (page = 1, size = 10) => {

    },
  },

}

export default nim;
